﻿#ifndef _CLOG_MANAGER_HPP_
#define _CLOG_MANAGER_HPP_

#include <map>
#include <list>
#include <time.h>
#include <assert.h>
#include <string.h>
#include "Base.hpp"
#include "CLock.hpp"
#include "Semaphore.hpp"
#include "SearchFile.hpp"

#if defined(_WIN32)
#include <process.h>
#pragma warning(disable:4996)
#elif defined(__linux__)
#include <unistd.h>
#endif

using namespace std;

#define DEFAULT_DAY		7
#define DEFAULT_HOUR	1
#define DEFAULT_LINE	50

static const char* strLogLevel[LOGTYPE_SIZE] = {"_Info.log","_Warn.log","_Error.log","_Debug.log"};
static const char* strLevelCtx[LOGTYPE_SIZE] = {"[Info]: ","[Warn]: ","[Error]: ","[Debug]: "};

class CLogData
{
public:
	CLogData()
	: fp(NULL)
	, lines(0)
	, overtime(0)
	, flgs(0)
	{

	}

public:
	FILE* fp;
	unsigned int lines;
	string logcontent;
	string logname;
	time_t overtime;
	CLock _lock; 
	long flgs;
};

class CLogManager
{
typedef std::map<string,CLogData*>TLogDataMap;
typedef std::list<string>TLogList;
public:
	CLogManager()
		: printLevel(PRINT_ALL)
		, printscreen(true)
		, flushlines(DEFAULT_LINE)
		, expiredays(DEFAULT_DAY)
		, timeinterval(DEFAULT_HOUR)
		, fCallback(NULL)
		, param(NULL)
		, threadrun(true)
		, threadhandle(0)
	{
		char temp[260] = {0};
		if(GetCurrentModuleName(temp,260))
		{
			string path_name = temp;
			size_t pos = path_name.rfind('/');
			if (pos != string::npos)
			{
				exename = path_name.substr(pos+1,path_name.length() - pos);
				logdir = path_name.substr(0,pos+1);
			}
		}
		else
		{
			perror("CLogManager Construct ModuleName");
			throw errno;
		}

		if(!CreateNewThread(threadhandle,CleanOldLogThWorker,this,0,0))
		{
			perror("CLogManager Construct Thread");
			throw errno;
		}

		char pid[20] = {0};
		sprintf(pid,"(pid-%d)",getpid());
		strpid = pid;
	}

	~CLogManager()
	{
		FlushLog();

		CLogData* pLogData = NULL;
		TLogDataMap::iterator it;
		for (it = customlogdatamap.begin(); it != customlogdatamap.end(); it++)
		{
			pLogData = it->second;
			for (int l=0; l<LOGTYPE_SIZE; l++)
			{
				if(pLogData[l].fp != NULL)
				{
					fclose(pLogData[l].fp);
				}
			}

			delete []pLogData;
		}

		pLogData = logdata;
		for (int i=0; i<LOGTYPE_SIZE; i++)
		{
			if(pLogData[i].fp != NULL)
			{
				fclose(pLogData[i].fp);
			}
		}
		threadrun = false;
		semaphore.post();
		CloseThread(threadhandle);
	}

public:
	void WriteLog(const char* prefix,int LogLevel,const char* log,unsigned int len)
	{
		if (fCallback)
			fCallback(LogLevel,prefix,log,len,param);

		if ((printLevel == PRINT_ALL) || ((printLevel >> LogLevel) & 0x00000001))
		{
			CLogData* pLogData = NULL;
			if (prefix != NULL)
			{
				CAutoLock _lock(lock);
				TLogDataMap::iterator it = customlogdatamap.find(prefix);
				if (it == customlogdatamap.end())
				{
					pLogData = new CLogData[LOGTYPE_SIZE];
					customlogdatamap[prefix] = pLogData;
				}
				else
				{
					pLogData = it->second;
				}
			}
			else
			{
				pLogData = logdata;
			}

			WriteLogEx(pLogData,LogLevel,log,len,prefix);
		}	
	}

	void FlushLog(CLogData *pLogData = NULL)
	{
		if (pLogData == NULL)
		{
			CLogData* ppLogData = NULL;
			TLogDataMap::iterator it;
			for (it = customlogdatamap.begin(); it != customlogdatamap.end(); it++)
			{
				ppLogData = it->second;
				for (int l=0; l<LOGTYPE_SIZE; l++)
				{
					if(ppLogData[l].fp != NULL)
					{
						fwrite(ppLogData[l].logcontent.c_str(),ppLogData[l].logcontent.length(),1,ppLogData[l].fp);
						ppLogData[l].logcontent.clear();
						ppLogData[l].lines = 0;
						fflush(ppLogData[l].fp);
					}
				}
			}

			ppLogData = logdata;
			for (int l=0; l<LOGTYPE_SIZE; l++)
			{
				if(ppLogData[l].fp != NULL)
				{
					fwrite(ppLogData[l].logcontent.c_str(),ppLogData[l].logcontent.length(),1,ppLogData[l].fp);
					ppLogData[l].logcontent.clear();
					ppLogData[l].lines = 0;
					fflush(ppLogData[l].fp);
				}
			}
		}
		else
		{
			fwrite(pLogData->logcontent.c_str(),pLogData->logcontent.length(),1,pLogData->fp);
			pLogData->logcontent.clear();
			pLogData->lines = 0;
			fflush(pLogData->fp);
		}
	}

	static CLogManager& Instance()
	{
		static CLogManager logManager;
		return logManager;
	}

private:
	const char* MakeLogName(CLogData *pLogData,int LogLevel,const char* prefix = NULL)
	{
		time_t t = time(NULL);
		pLogData->overtime = t + timeinterval * 60 * 60;

		struct tm* st = localtime(&t);
		char strTime[25] = {0};
		strftime (strTime,sizeof(strTime),"%Y_%m_%d-%H_%M_%S_",st);

		pLogData->logname.clear();
		pLogData->logname.append(logdir);

		string logname;
		logname.append(strTime);
		
		if (prefix != NULL)
		{
			logname.append(prefix);
		}
		else
		{
			logname.append(exename);
		}

		logname.append(strpid);
		logname.append(strLogLevel[LogLevel]);

		{
			CAutoLock _lock(_loglistlock);
			loglist.push_back(logname);
		}

		pLogData->logname.append(logname);
		return pLogData->logname.c_str();
	}

	int CheckChangeFile(CLogData *pLogData,int LogLevel,time_t nowTime,const char* prefix = NULL)
	{
		if (pLogData->fp == NULL)
		{
			const char* logname = MakeLogName(pLogData,LogLevel,prefix);
			pLogData->fp = fopen(logname, "wb+");  // Make a FILE*.
			if (pLogData->fp == NULL)
			{
				perror("CLogManager CheckChangeFile");
				return -1;
			}

			return 1;
		}
		else if(nowTime >= pLogData->overtime)
		{
			FlushLog(pLogData);

			fclose(pLogData->fp);
			pLogData->fp = NULL;

			return CheckChangeFile(pLogData,LogLevel,nowTime,prefix);
		}
		else
		{
			return 0;
		}
	}

	void WriteLogEx(CLogData *pLogData,int LogLevel,const char* log,unsigned int len,const char* prefix = NULL)
	{
		time_t t = time(NULL);
		struct tm* st = localtime(&t);
		char strTime[25] = {0};
		strftime (strTime,sizeof(strTime),"%Y-%m-%d %H:%M:%S ",st);

		string currentlog(strTime);
		currentlog.append(strLevelCtx[LogLevel]);
		currentlog.append(log,len);
		currentlog.append("\r\n");

		if (printscreen)
		{
			fwrite(currentlog.c_str(),currentlog.length(),1,stderr);
		}

		CAutoLock autolock(pLogData[LogLevel]._lock);
		if(CheckChangeFile(&pLogData[LogLevel],LogLevel,t,prefix)<0)
		{	
			return;
		}

		pLogData[LogLevel].logcontent.append(currentlog);
		pLogData[LogLevel].lines++;

		if (pLogData[LogLevel].lines >= flushlines)
		{
			FlushLog(&pLogData[LogLevel]);
		}
	}

	static bool result(void* pUser, bool isDir, const char* name)
	{
		CLogManager* pLogManager = (CLogManager*)pUser;
		if (!isDir)
		{
			CAutoLock _lock(pLogManager->_loglistlock);
			pLogManager->loglist.push_back(name);
		}

		return true;
	}

	static int CleanOldLogThWorker(void* param)
	{
		CLogManager* pLogManager = (CLogManager*)param;
		pLogManager->CleanLog();
		return 1;
	}

	void CleanLog()
	{
		struct tm st;
		char strTime[25] = {'\0'};
		time_t tlog = -1;

		semaphore.wait(5 * 1000);//启动后等待5秒开始清理日志

		while (threadrun)
		{
			{
				CAutoLock _lock(_loglistlock);
				loglist.clear();
			}

			SearchFile(logdir.c_str(), "*.log", result, this);

			long expiresecs = expiredays * 24 * 3600;
			time_t t = time(NULL);
			TLogList::iterator it;
			for (it = loglist.begin(); it != loglist.end();)
			{
				strncpy(strTime,(*it).c_str(),19);
				sscanf(strTime,"%4d_%2d_%2d-%2d_%2d_%2d",&st.tm_year,&st.tm_mon,&st.tm_mday,&st.tm_hour,&st.tm_min,&st.tm_sec);
				st.tm_year -= 1900;
				st.tm_mon--;

				tlog = mktime(&st);
				if (tlog == -1)
				{
					CAutoLock _lock(_loglistlock);
					loglist.erase(it++);
					continue;
				}

				if (tlog + expiresecs < t)
				{
					string logname = logdir;
					logname.append(*it);
					remove(logname.c_str());

					CAutoLock _lock(_loglistlock);
					loglist.erase(it++);
					continue;
				}

				it++;
			}

			semaphore.wait(30*60*1000);//半个小时清理一次
		}	
	}
public:
	CLock lock;
	string logdir;
	unsigned int printLevel;
	bool printscreen;
	unsigned int flushlines;
	unsigned int expiredays;
	unsigned int timeinterval;
	LibLogCallback fCallback;
	void* param;
private:
	CLock _loglistlock;
	bool threadrun;
	string strpid;
	string exename;
	ThreadHandle threadhandle;
	TLogDataMap customlogdatamap;
	TLogList	loglist;
	CLogData logdata[LOGTYPE_SIZE];
	Semaphore semaphore;
};

#endif
